このノートブックの実行例はこちら(HTML版)で確認できます
ページ上部のメニューバーにある Kernel メニューをクリックし、プルダウンメニューから [Change Kernel ...] を選び、gssm2023:Python を選択してください。
ノートブック上部の右隅に表示されたカーネル名が gssm2023:Python になっていることを確認してください。
以下のセルを修正せずに実行してください
import warnings
warnings.simplefilter('ignore')
import random
import numpy as np
seed = 42
random.seed(seed)
np.random.seed(seed)
# ワードクラウドを描画する
def plot_wordcloud(word_str, width=6, height=4):
import matplotlib.pyplot as plt
%matplotlib inline
fig = plt.figure(figsize=(width, height))
ax = fig.add_subplot(1, 1, 1)
plot_wordcloud_ax(ax, word_str)
plt.axis("off")
plt.tight_layout()
plt.show()
def plot_wordcloud_ax(ax, word_str):
font_path = !find ${HOME} -name "ipaexg.ttf"
# font_path = ['/Library/Fonts/Arial Unicode.ttf']
import wordcloud
wc = wordcloud.WordCloud(
background_color='white',
font_path=font_path[0],
max_font_size=100)
img = wc.generate(word_str)
ax.imshow(img, interpolation='bilinear')
# トピックモデルによるワードクラウドを描画する
def plot_topic_model(lda, feature_names, n_top_words=20, width=10, height=4):
font_path = !find ${HOME} -name "ipaexg.ttf"
# font_path = ['/Library/Fonts/Arial Unicode.ttf']
import matplotlib.pyplot as plt
import wordcloud
%matplotlib inline
fig = plt.figure(figsize=(width, height))
for topic_idx, topic in enumerate(lda.components_):
sorted_text = ' '.join([feature_names[i] for i in topic.argsort()[:-n_top_words-1:-1]])
wc = wordcloud.WordCloud(
background_color='white',
font_path=font_path[0],
max_font_size=100)
ax = fig.add_subplot(2, 3, topic_idx + 1)
img = wc.generate(sorted_text)
ax.imshow(img, interpolation='bilinear')
ax.set_title(f"Topic # {topic_idx+1}:")
plt.tight_layout()
plt.show()
# 共起ネットワークを描画する (抽出語-抽出語用)
def plot_cooccur_network(df, word_counts, cutoff, width=8, height=8):
import matplotlib.pyplot as plt
import japanize_matplotlib
plt.figure(figsize=(width, height))
fig = plt.figure(figsize=(width, height))
ax = fig.add_subplot(1, 1, 1)
plot_cooccur_network_ax(ax, df, word_counts, cutoff)
plt.axis("off")
plt.show()
def plot_cooccur_network_ax(ax, df, word_counts, cutoff):
import numpy as np
import networkx as nx
from networkx.algorithms import community
from networkx.drawing.nx_agraph import graphviz_layout
import matplotlib.pyplot as plt
import japanize_matplotlib
%matplotlib inline
Xc = df.values
Xc_max = Xc.max()
words = df.columns
count_max = word_counts.max()
weights_w, weights_c = [], []
for i, j in zip(*Xc.nonzero()):
if i < j and Xc[i,j] > cutoff:
weights_w.append((words[i], {'weight': word_counts[i] / count_max}))
weights_w.append((words[j], {'weight': word_counts[j] / count_max}))
weights_c.append((words[i], words[j], Xc[i,j] / Xc_max))
G = nx.Graph()
G.add_nodes_from(weights_w)
G.add_weighted_edges_from(weights_c)
G.remove_nodes_from(list(nx.isolates(G)))
# G = nx.minimum_spanning_tree(G)
# pos = nx.spring_layout(G, k=0.3)
pos = graphviz_layout(G, prog='neato', args='-Goverlap="scalexy" -Gsep="+6" -Gnodesep=0.8 -Gsplines="polyline" -GpackMode="graph" -Gstart={}'.format(43))
weights_n = np.array(list(nx.get_node_attributes(G, 'weight').values()))
weights_e = np.array(list(nx.get_edge_attributes(G, 'weight').values()))
communities = community.greedy_modularity_communities(G)
color_map = []
for node in G:
for i, c in enumerate(communities):
if node in c:
color_map.append(i)
nx.draw_networkx_nodes(G, pos, node_color=color_map, alpha=0.7, cmap=plt.cm.Set2, node_size=5000 * weights_n, ax=ax)
nx.draw_networkx_edges(G, pos, edge_color='gray', edge_cmap=plt.cm.Blues, alpha=0.7, width=3 * weights_e, ax=ax)
nx.draw_networkx_labels(G, pos, font_family='IPAexGothic', ax=ax)
# ax.axis('off')
# 共起ネットワークを描画する (外部変数-抽出語用)
def plot_attrs_network(df, attr_counts, word_counts, cutoff, width=8, height=8):
import numpy as np
import networkx as nx
import matplotlib.pyplot as plt
import japanize_matplotlib
from networkx.drawing.nx_agraph import graphviz_layout
%matplotlib inline
Xc = df.values
Xc_max = Xc.max()
attrs = list(df.index)
attr_count_max = attr_counts.max()
words = list(df.columns)
word_count_max = word_counts.max()
weights_n, weights_c = [], []
for i, j in zip(*Xc.nonzero()):
if Xc[i,j] > cutoff:
weights_n.append((attrs[i], {'weight': attr_counts[i] / attr_count_max, 'type': 'attr'}))
weights_n.append((words[j], {'weight': word_counts[j] / word_count_max, 'type': 'word'}))
weights_c.append((attrs[i], words[j], Xc[i,j] / Xc_max))
G = nx.Graph()
G.add_nodes_from(weights_n)
G.add_weighted_edges_from(weights_c)
G.remove_nodes_from(list(nx.isolates(G)))
# G = nx.minimum_spanning_tree(G)
plt.figure(figsize=(width, height))
# pos = nx.spring_layout(G, k=0.3)
pos = graphviz_layout(G, prog='neato', args='-Goverlap="scalexy" -Gsep="+6" -Gnodesep=0.8 -Gsplines="polyline" -GpackMode="graph" -Gstart={}'.format(43))
nodelist_a = [node for node in G.nodes if G.nodes[node]['type'] == 'attr']
nodelist_w = [node for node in G.nodes if G.nodes[node]['type'] == 'word']
weights_a = np.array([G.nodes[node]['weight'] for node in G.nodes if G.nodes[node]['type'] == 'attr'])
weights_w = np.array([G.nodes[node]['weight'] for node in G.nodes if G.nodes[node]['type'] == 'word'])
weights_e = np.array(list(nx.get_edge_attributes(G, 'weight').values()))
color_map = []
for node in G:
if G.nodes[node]['type'] == 'word':
color_map.append(G.degree(node)+3)
nx.draw_networkx_nodes(G, pos, node_color='lightsalmon', alpha=0.7, cmap=plt.cm.Set2, node_size=1000 * weights_a, nodelist=nodelist_a, node_shape='s')
nx.draw_networkx_nodes(G, pos, node_color=color_map, alpha=0.7, cmap=plt.cm.Set2, node_size=5000 * weights_w, nodelist=nodelist_w)
nx.draw_networkx_edges(G, pos, edge_color='gray', edge_cmap=plt.cm.Blues, alpha=0.7, width=3 * weights_e)
nx.draw_networkx_labels(G, pos, font_family='IPAexGothic')
plt.axis("off")
plt.show()
# 係り受けによる共起ネットワークを描画する
def plot_dependency_network(df, word_counts, cutoff, width=8, height=8):
import numpy as np
import networkx as nx
from networkx.algorithms import community
import matplotlib.pyplot as plt
import japanize_matplotlib
from networkx.drawing.nx_agraph import graphviz_layout
%matplotlib inline
Xc = df.values
Xc_max = Xc.max()
words = df.columns
count_max = word_counts.max()
weights_w, weights_c = [], []
for i, j in zip(*Xc.nonzero()):
if i != j and Xc[i,j] > cutoff:
weights_w.append((words[i], {'weight': word_counts[i] / count_max}))
weights_w.append((words[j], {'weight': word_counts[j] / count_max}))
weights_c.append((words[i], words[j], Xc[i,j] / Xc_max))
G = nx.DiGraph()
G.add_nodes_from(weights_w)
G.add_weighted_edges_from(weights_c)
G.remove_nodes_from(list(nx.isolates(G)))
# G = nx.minimum_spanning_tree(G)
plt.figure(figsize=(width, height))
# pos = nx.spring_layout(G, k=0.3)
pos = graphviz_layout(G, prog='neato', args='-Goverlap="scalexy" -Gsep="+6" -Gnodesep=0.8 -Gsplines="polyline" -GpackMode="graph" -Gstart={}'.format(43))
weights_n = np.array(list(nx.get_node_attributes(G, 'weight').values()))
weights_e = np.array(list(nx.get_edge_attributes(G, 'weight').values()))
communities = community.greedy_modularity_communities(G)
color_map = []
for node in G:
for i, c in enumerate(communities):
if node in c:
color_map.append(i)
nx.draw_networkx_nodes(G, pos, node_color=color_map, alpha=0.7, cmap=plt.cm.Set2, node_size=5000 * weights_n)
nx.draw_networkx_edges(G, pos, edge_color='gray', edge_cmap=plt.cm.Blues, alpha=0.7, width=3 * weights_e)
nx.draw_networkx_labels(G, pos, font_family='IPAexGothic')
plt.axis("off")
plt.show()
# 対応分析の結果をプロットする
def plot_coresp(row_coord, col_coord, row_labels, col_labels, explained_inertia=None, width=8, height=8):
import matplotlib.pyplot as plt
import japanize_matplotlib
%matplotlib inline
plt.figure(figsize=(width, height))
# Plot of rows (外部変数)
plt.plot(row_coord[:, 0], row_coord[:, 1], "*", color='red', alpha=0.5)
for i, label in enumerate(row_labels):
plt.text(row_coord[i, 0], row_coord[i, 1], label, color='red', ha='left', va='bottom')
# Plot of columns (単語)
plt.plot(col_coord[:, 0], col_coord[:, 1], "o", color='blue', alpha=0.5)
for i, label in enumerate(col_labels):
plt.text(col_coord[i, 0], col_coord[i, 1], label, color='blue', ha='left', va='bottom')
plt.axvline(0, linestyle='dashed', color='gray', alpha=0.5)
plt.axhline(0, linestyle='dashed', color='gray', alpha=0.5)
if explained_inertia is not None:
plt.xlabel(f"Dim 1 ({explained_inertia[0]:.3f}%)")
plt.ylabel(f"Dim 2 ({explained_inertia[1]:.3f}%)")
# plt.axis('equal')
plt.show()
# PCA の結果をプロットする
def plot_pca(coeff, reduced, row_labels, col_labels, var_ratio=None, width=8, height=8):
import matplotlib.pyplot as plt
import japanize_matplotlib
%matplotlib inline
plt.figure(figsize=(width, height))
# Plot of rows (外部変数)
for i, label in enumerate(row_labels):
plt.arrow(0, 0, coeff[i,0], coeff[i,1], color='r', alpha=0.5)
plt.text(coeff[i, 0], coeff[i, 1], label, color='red', ha='left', va='bottom')
# Plot of columns (単語)
plt.plot(reduced[:, 0], reduced[:, 1], "o", color='blue', alpha=0.5)
for i, label in enumerate(col_labels):
plt.text(reduced[i, 0], reduced[i, 1], label, color='blue', ha='left', va='bottom')
plt.axvline(0, linestyle='dashed', color='gray', alpha=0.5)
plt.axhline(0, linestyle='dashed', color='gray', alpha=0.5)
if var_ratio is not None:
plt.xlabel(f"Dim 1 ({var_ratio[0]*100:.3f}%)")
plt.ylabel(f"Dim 2 ({var_ratio[1]*100:.3f}%)")
# plt.axis('equal')
plt.show()
# 共起頻度行列を Jaccard 係数行列に変換する (抽出語-抽出語用)
def jaccard_coef(cooccur_df, cross_df):
import numpy as np
import pandas as pd
Xc = cooccur_df.values
Xj = np.zeros(Xc.shape)
Xc_sum = cross_df.sum(axis=0).values
for i, j in zip(*Xc.nonzero()):
if i < j:
Xj[i,j] = Xc[i,j] / (Xc_sum[i] + Xc_sum[j] - Xc[i,j])
jaccard_df = pd.DataFrame(Xj, columns=cooccur_df.columns, index=cooccur_df.columns)
return jaccard_df
# 共起頻度行列を Jaccard 係数行列に変換する (外部変数-抽出語用)
def jaccard_attrs_coef(df, attr_counts, word_counts, total=10000, conditional=False):
import numpy as np
import pandas as pd
Xc = df.values
Xj = np.zeros(df.shape)
for i, j in zip(*Xc.nonzero()):
if not conditional:
conditional_prob = Xc[i,j] / attr_counts[i]
assumption_prob = word_counts[j] / total
if conditional_prob > assumption_prob:
Xj[i,j] = Xc[i,j] / (attr_counts[i] + word_counts[j] - Xc[i,j])
else:
Xj[i,j] = .0
else:
Xj[i,j] = Xc[i,j] / (attr_counts[i] + word_counts[j] - Xc[i,j])
jaccard_df = pd.DataFrame(Xj, columns=df.columns, index=df.index)
return jaccard_df
以下のデータがダウンロード済みです
| ファイル名 | 件数 | データセット | 備考 |
|---|---|---|---|
| rakuten-1000-2022-2023.xlsx.zip | 10,000 | •レジャー+ビジネスの 10エリア •エリアごと 1,000件 (ランダムサンプリング) •期間: 2022/1~2023 GW明け |
本講義の全体を通して使用する |
# もし、再度ダウンロードが必要な場合は残りの行のコメントマーク「#」を除去して、このセルを再実行してください
# FILE_ID = "1n-uvGoH7XQhxexN57hYXuFrkGeHKp-HV"
# !gdown --id {FILE_ID}
# !unzip rakuten-1000-2022-2023.xlsx.zip
import numpy as np
import pandas as pd
all_df = pd.read_excel("rakuten-1000-2022-2023.xlsx")
print(all_df.shape)
display(all_df.head())
(10000, 18)
| カテゴリー | エリア | 施設番号 | 施設名 | コメント | 総合 | サービス | 立地 | 部屋 | 設備・アメニティ | 風呂 | 食事 | 旅行の目的 | 同伴者 | 宿泊年月 | 投稿者 | 年代 | 性別 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | A_レジャー | 01_登別 | 5500 | 登別 石水亭 | お風呂が男女時間交代の屋上風呂に入れなかったのが残念でした。食事はスタッフの皆さんがとても親... | 5 | 5 | 4 | 5 | 5.0 | 4.0 | 5.0 | レジャー | 家族 | 44743 | さとちん4359 | 60代 | 女性 |
| 1 | A_レジャー | 01_登別 | 39175 | 登別温泉 登別グランドホテル | 大浴場のアメニティ、洗い場のクレンジング、ソープ、パック等が充実していて、大変良かったです。... | 4 | 5 | 4 | 3 | 5.0 | 5.0 | 5.0 | レジャー | 家族 | 44835 | まなちゃん5695 | 60代 | 女性 |
| 2 | A_レジャー | 01_登別 | 20547 | 北湯沢温泉郷 湯元 ホロホロ山荘 | 犬プラン素泊まり一名で宿泊しましたが、夜8時頃売店でお土産(その時飲む飲み物など)を買う時に... | 1 | 1 | 2 | 2 | 3.0 | 4.0 | NaN | レジャー | 一人 | 44835 | 投稿者 | na | na |
| 3 | A_レジャー | 01_登別 | 139962 | ザ レイクビュー TOYA 乃の風リゾート | 部屋風呂が最高でした。 | 5 | 5 | 5 | 5 | 5.0 | 5.0 | 5.0 | レジャー | 恋人 | 44986 | 投稿者 | na | na |
| 4 | A_レジャー | 01_登別 | 80732 | 登別カルルス温泉 湯元オロフレ荘 | カルルス温泉郷の静かな立地、そして湯元の素晴らしい泉質、美味しいお料理、飾らない中にも親切な... | 5 | 5 | 5 | 5 | 5.0 | 5.0 | 5.0 | レジャー | 家族 | 44986 | 投稿者 | na | na |
コメント列から単語を抽出する (単語を品詞「名詞」「形容詞」「未知語」で絞り込む)
from collections import defaultdict
import MeCab
tagger = MeCab.Tagger("-r ../tools/usr/local/etc/mecabrc --unk-feature 未知語")
word_counts = defaultdict(lambda: 0)
words = []
ZEN = "".join(chr(0xff01 + i) for i in range(94))
HAN = "".join(chr(0x21 + i) for i in range(94))
HAN2ZEN = str.maketrans(HAN, ZEN)
# stopwords = ['する', 'ある', 'ない', 'いう', 'もの', 'こと', 'よう', 'なる', 'ほう']
stopwords = ["湯畑"]
for index, row in all_df.iterrows():
node = tagger.parseToNode(row["コメント"].translate(HAN2ZEN))
while node:
features = node.feature.split(',')
pos1 = features[0]
pos2 = features[1] if len(features) > 1 else ""
base = features[6] if len(features) > 6 else None
if base not in stopwords:
if (pos1 == "名詞" and pos2 == "一般"):
base = base if base is not None else node.surface
postag = "名詞"
key = (base, postag)
word_counts[key] += 1
words.append([index + 1, base, postag, row["カテゴリー"], row["エリア"], key])
elif (pos1 == "名詞" and pos2 == "形容動詞語幹"):
base = base if base is not None else node.surface
base = f"{base}だ"
postag = "形容動詞"
key = (base, postag)
word_counts[key] += 1
words.append([index + 1, base, postag, row["カテゴリー"], row["エリア"], key])
elif pos1 == "形容詞":
base = base if base is not None else node.surface
postag = "形容詞"
key = (base, postag)
word_counts[key] += 1
words.append([index + 1, base, postag, row["カテゴリー"], row["エリア"], key])
elif pos1 == "未知語":
base = base if base is not None else node.surface
postag = "未知語"
key = (base, postag)
word_counts[key] += 1
words.append([index + 1, base, postag, row["カテゴリー"], row["エリア"], key])
node = node.next
columns = [
"文書ID",
# "単語ID",
"表層",
"品詞",
"カテゴリー",
"エリア",
"dict_key",
]
docs_df = pd.DataFrame(words, columns=columns)
print(docs_df.shape)
display(docs_df.head())
(149372, 6)
| 文書ID | 表層 | 品詞 | カテゴリー | エリア | dict_key | |
|---|---|---|---|---|---|---|
| 0 | 1 | 風呂 | 名詞 | A_レジャー | 01_登別 | (風呂, 名詞) |
| 1 | 1 | 男女 | 名詞 | A_レジャー | 01_登別 | (男女, 名詞) |
| 2 | 1 | 屋上 | 名詞 | A_レジャー | 01_登別 | (屋上, 名詞) |
| 3 | 1 | 風呂 | 名詞 | A_レジャー | 01_登別 | (風呂, 名詞) |
| 4 | 1 | 残念だ | 形容動詞 | A_レジャー | 01_登別 | (残念だ, 形容動詞) |
抽出語の出現頻度をカウントする
word_list = []
for i, (k, v) in enumerate(sorted(word_counts.items(), key=lambda x:x[1], reverse=True)):
word_list.append((i, k[0], v, k))
columns = [
"単語ID",
"表層",
"出現頻度",
"dict_key"
]
word_counts_df = pd.DataFrame(word_list, columns=columns)
print(word_counts_df.shape)
display(word_counts_df.head(10))
(8390, 4)
| 単語ID | 表層 | 出現頻度 | dict_key | |
|---|---|---|---|---|
| 0 | 0 | 部屋 | 6689 | (部屋, 名詞) |
| 1 | 1 | 良い | 5257 | (良い, 形容詞) |
| 2 | 2 | ホテル | 2831 | (ホテル, 名詞) |
| 3 | 3 | 風呂 | 2702 | (風呂, 名詞) |
| 4 | 4 | 美味しい | 2249 | (美味しい, 形容詞) |
| 5 | 5 | ない | 2124 | (ない, 形容詞) |
| 6 | 6 | スタッフ | 1712 | (スタッフ, 名詞) |
| 7 | 7 | 温泉 | 1705 | (温泉, 名詞) |
| 8 | 8 | よい | 1446 | (よい, 形容詞) |
| 9 | 9 | 立地 | 1374 | (立地, 名詞) |
単語IDを紐つける (出現回数 Top 150語のみ抽出する)
word_counts_150_df = word_counts_df[0:150]
merged_df = pd.merge(docs_df, word_counts_150_df, how="inner", on="dict_key", suffixes=["", "_right"])
docs_150_df = merged_df[["文書ID", "単語ID", "表層", "品詞", "カテゴリー", "エリア", "dict_key"]]
print(docs_150_df.shape)
display(docs_150_df)
(83459, 7)
| 文書ID | 単語ID | 表層 | 品詞 | カテゴリー | エリア | dict_key | |
|---|---|---|---|---|---|---|---|
| 0 | 1 | 3 | 風呂 | 名詞 | A_レジャー | 01_登別 | (風呂, 名詞) |
| 1 | 1 | 3 | 風呂 | 名詞 | A_レジャー | 01_登別 | (風呂, 名詞) |
| 2 | 4 | 3 | 風呂 | 名詞 | A_レジャー | 01_登別 | (風呂, 名詞) |
| 3 | 8 | 3 | 風呂 | 名詞 | A_レジャー | 01_登別 | (風呂, 名詞) |
| 4 | 15 | 3 | 風呂 | 名詞 | A_レジャー | 01_登別 | (風呂, 名詞) |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 83454 | 9933 | 149 | スペース | 名詞 | B_ビジネス | 10_福岡 | (スペース, 名詞) |
| 83455 | 9937 | 149 | スペース | 名詞 | B_ビジネス | 10_福岡 | (スペース, 名詞) |
| 83456 | 9943 | 149 | スペース | 名詞 | B_ビジネス | 10_福岡 | (スペース, 名詞) |
| 83457 | 9980 | 149 | スペース | 名詞 | B_ビジネス | 10_福岡 | (スペース, 名詞) |
| 83458 | 9980 | 149 | スペース | 名詞 | B_ビジネス | 10_福岡 | (スペース, 名詞) |
83459 rows × 7 columns
「文書-抽出語」表 を作成する
word_counts_75_df = word_counts_df[0:75]
merged_df = pd.merge(docs_df, word_counts_75_df, how="inner", on="dict_key", suffixes=["", "_right"])
docs_75_df = merged_df[["文書ID", "単語ID", "表層", "品詞", "カテゴリー", "エリア", "dict_key"]]
cross_75_df = pd.crosstab(
[
docs_75_df['カテゴリー'],
docs_75_df['エリア'],
docs_75_df['文書ID']
],
docs_75_df['単語ID'], margins=False
)
cross_75_df.columns = word_counts_75_df["表層"]
print(cross_75_df.shape)
display(cross_75_df)
(9630, 75)
| 表層 | 部屋 | 良い | ホテル | 風呂 | 美味しい | ない | スタッフ | 温泉 | よい | 立地 | ... | 新しい | 楽しい | 気持ち | 雰囲気 | 女性 | 高い | 建物 | すごい | 大きい | 量 | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| カテゴリー | エリア | 文書ID | |||||||||||||||||||||
| A_レジャー | 01_登別 | 1 | 0 | 0 | 0 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 2 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 3 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 4 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 5 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| B_ビジネス | 10_福岡 | 9996 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 9997 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | ||
| 9998 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 9999 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 10000 | 0 | 2 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
9630 rows × 75 columns
「文書-抽出語」 表を {0,1} に変換する
cross_75_df[cross_75_df > 0] = 1
print(cross_75_df.shape)
display(cross_75_df)
(9630, 75)
| 表層 | 部屋 | 良い | ホテル | 風呂 | 美味しい | ない | スタッフ | 温泉 | よい | 立地 | ... | 新しい | 楽しい | 気持ち | 雰囲気 | 女性 | 高い | 建物 | すごい | 大きい | 量 | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| カテゴリー | エリア | 文書ID | |||||||||||||||||||||
| A_レジャー | 01_登別 | 1 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 2 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 3 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 4 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 5 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| B_ビジネス | 10_福岡 | 9996 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 9997 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | ||
| 9998 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 9999 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 10000 | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
9630 rows × 75 columns
共起度行列を作成する (抽出語-抽出語)
from scipy.sparse import csc_matrix
X = cross_75_df.values
X = csc_matrix(X)
Xc = (X.T * X)
Xc = np.triu(Xc.toarray())
cooccur_75_df = pd.DataFrame(Xc, columns=cross_75_df.columns, index=cross_75_df.columns)
print(cooccur_75_df.shape)
display(cooccur_75_df.head())
(75, 75)
| 表層 | 部屋 | 良い | ホテル | 風呂 | 美味しい | ない | スタッフ | 温泉 | よい | 立地 | ... | 新しい | 楽しい | 気持ち | 雰囲気 | 女性 | 高い | 建物 | すごい | 大きい | 量 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 表層 | |||||||||||||||||||||
| 部屋 | 4568 | 1961 | 1091 | 1273 | 1068 | 1012 | 783 | 706 | 676 | 691 | ... | 200 | 175 | 192 | 190 | 183 | 187 | 177 | 190 | 186 | 174 |
| 良い | 0 | 3701 | 892 | 1026 | 896 | 692 | 712 | 638 | 339 | 701 | ... | 140 | 121 | 201 | 175 | 142 | 136 | 140 | 133 | 132 | 168 |
| ホテル | 0 | 0 | 2041 | 391 | 354 | 506 | 411 | 235 | 267 | 356 | ... | 120 | 70 | 101 | 80 | 102 | 102 | 53 | 78 | 73 | 56 |
| 風呂 | 0 | 0 | 0 | 2143 | 598 | 503 | 376 | 359 | 333 | 317 | ... | 83 | 102 | 112 | 100 | 101 | 84 | 93 | 77 | 134 | 120 |
| 美味しい | 0 | 0 | 0 | 0 | 1962 | 379 | 432 | 429 | 229 | 215 | ... | 67 | 118 | 92 | 97 | 74 | 69 | 85 | 93 | 81 | 142 |
5 rows × 75 columns
word_counts = cross_75_df.sum(axis=0).values
plot_cooccur_network(cooccur_75_df, word_counts, cooccur_75_df.values.max() * 0.05)
<Figure size 800x800 with 0 Axes>
jaccard_75_df = jaccard_coef(cooccur_75_df, cross_75_df)
word_counts = cross_75_df.sum(axis=0).values
plot_cooccur_network(jaccard_75_df, word_counts, np.sort(jaccard_75_df.values.reshape(-1))[::-1][60])
<Figure size 800x800 with 0 Axes>
「文書-抽出語」 表を確認する
print(cross_75_df.shape)
display(cross_75_df.head())
(9630, 75)
| 表層 | 部屋 | 良い | ホテル | 風呂 | 美味しい | ない | スタッフ | 温泉 | よい | 立地 | ... | 新しい | 楽しい | 気持ち | 雰囲気 | 女性 | 高い | 建物 | すごい | 大きい | 量 | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| カテゴリー | エリア | 文書ID | |||||||||||||||||||||
| A_レジャー | 01_登別 | 1 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 2 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 3 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 4 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 5 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
5 rows × 75 columns
「外部変数-抽出語」 クロス集計表を作成する
aggregate_75_df = pd.concat([
cross_75_df.groupby(level='カテゴリー').sum(),
cross_75_df.groupby(level='エリア').sum()
])
print(aggregate_75_df.shape)
display(aggregate_75_df)
(12, 75)
| 表層 | 部屋 | 良い | ホテル | 風呂 | 美味しい | ない | スタッフ | 温泉 | よい | 立地 | ... | 新しい | 楽しい | 気持ち | 雰囲気 | 女性 | 高い | 建物 | すごい | 大きい | 量 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| A_レジャー | 2398 | 2046 | 757 | 1535 | 1430 | 880 | 888 | 1188 | 631 | 518 | ... | 136 | 251 | 202 | 218 | 127 | 161 | 215 | 161 | 166 | 260 |
| B_ビジネス | 2170 | 1655 | 1284 | 608 | 532 | 749 | 523 | 110 | 574 | 810 | ... | 187 | 65 | 113 | 87 | 160 | 134 | 87 | 117 | 123 | 30 |
| 01_登別 | 447 | 409 | 194 | 323 | 255 | 187 | 148 | 222 | 114 | 38 | ... | 25 | 42 | 32 | 26 | 22 | 33 | 54 | 38 | 42 | 24 |
| 02_草津 | 488 | 434 | 181 | 352 | 274 | 180 | 154 | 275 | 117 | 155 | ... | 23 | 53 | 43 | 33 | 25 | 30 | 53 | 28 | 34 | 57 |
| 03_箱根 | 548 | 436 | 134 | 326 | 355 | 202 | 212 | 212 | 133 | 57 | ... | 30 | 62 | 43 | 55 | 36 | 35 | 54 | 31 | 31 | 73 |
| 04_道後 | 416 | 349 | 191 | 181 | 174 | 130 | 135 | 225 | 137 | 176 | ... | 37 | 32 | 31 | 29 | 13 | 28 | 32 | 25 | 28 | 29 |
| 05_湯布院 | 499 | 418 | 57 | 353 | 372 | 181 | 239 | 254 | 130 | 92 | ... | 21 | 62 | 53 | 75 | 31 | 35 | 22 | 39 | 31 | 77 |
| 06_札幌 | 452 | 346 | 255 | 121 | 129 | 151 | 114 | 38 | 103 | 166 | ... | 34 | 16 | 15 | 24 | 28 | 30 | 12 | 18 | 34 | 6 |
| 07_名古屋 | 434 | 310 | 241 | 116 | 97 | 144 | 102 | 18 | 133 | 141 | ... | 40 | 13 | 22 | 13 | 42 | 28 | 14 | 17 | 25 | 3 |
| 08_東京 | 441 | 338 | 240 | 106 | 99 | 131 | 99 | 12 | 104 | 166 | ... | 36 | 14 | 26 | 9 | 30 | 18 | 19 | 25 | 19 | 10 |
| 09_大阪 | 431 | 317 | 297 | 135 | 88 | 162 | 93 | 20 | 121 | 161 | ... | 32 | 10 | 27 | 24 | 37 | 26 | 20 | 31 | 19 | 3 |
| 10_福岡 | 412 | 344 | 251 | 130 | 119 | 161 | 115 | 22 | 113 | 176 | ... | 45 | 12 | 23 | 17 | 23 | 32 | 22 | 26 | 26 | 8 |
12 rows × 75 columns
import mca
ncols = aggregate_75_df.shape[1]
mca_ben = mca.MCA(aggregate_75_df, ncols=ncols, benzecri=False)
row_coord = mca_ben.fs_r(N=2)
col_coord = mca_ben.fs_c(N=2)
eigenvalues = mca_ben.L
total = np.sum(eigenvalues)
explained_inertia = 100 * eigenvalues / total
row_labels = aggregate_75_df.index
col_labels = aggregate_75_df.columns
plot_coresp(row_coord, col_coord,row_labels, col_labels, explained_inertia)
table_N = aggregate_75_df.values
row_sum = table_N.sum(axis=1)
col_sum = table_N.sum(axis=0)
n = aggregate_75_df.values.sum()
expected = np.outer(row_sum, col_sum) / n
chisq = np.square(table_N - expected) / expected
residuals = (table_N - expected) / np.sqrt(expected)
# Standardized residuals
residuals = residuals / np.sqrt(n)
# Number of dimensions
nb_axes = min(residuals.shape[0]-1, residuals.shape[1]-1)
# Singular value decomposition
U, s, V = np.linalg.svd(residuals, full_matrices=True)
sv = s[:nb_axes]
u = U[:, :nb_axes]
v = V[:nb_axes, :]
# row mass
row_mass = row_sum / n
# row coord = u * sv /sqrt(row.mass)
row_coord = (u * sv) / np.sqrt(row_mass)[:, np.newaxis]
# col mass
col_mass = col_sum / n
# row coord = sv * vT /sqrt(col.mass)
col_coord = (sv * v.T) / np.sqrt(col_mass)[:, np.newaxis]
# eige nvalue
eige_nvalue = s ** 2
# contribution rate
explained_inertia = 100 * eige_nvalue / sum(eige_nvalue)
row_labels = aggregate_75_df.index
col_labels = aggregate_75_df.columns
plot_coresp(row_coord, col_coord,row_labels, col_labels, explained_inertia)
import numpy as np
table_N = aggregate_75_df.values
table_P = table_N / aggregate_75_df.values.max()
# Singular value decomposition
U, s, V = np.linalg.svd(table_P, full_matrices=True)
sv = s[:nb_axes]
u = U[:, :nb_axes]
v = V[:nb_axes, :]
# row mass
row_mass = row_sum / n
# row coord = u * sv /sqrt(row.mass)
row_coord = (u * sv) / np.sqrt(row_mass)[:, np.newaxis]
# col mass
col_mass = col_sum / n
# row coord = sv * vT /sqrt(col.mass)
col_coord = (sv * v.T) / np.sqrt(col_mass)[:, np.newaxis]
# eige nvalue
eige_nvalue = s ** 2
# contribution rate
explained_inertia = 100 * eige_nvalue / sum(eige_nvalue)
row_labels = aggregate_75_df.index
col_labels = aggregate_75_df.columns
plot_coresp(row_coord, col_coord,row_labels, col_labels, explained_inertia)
from sklearn.decomposition import PCA
table_N = aggregate_75_df.values
pca = PCA()
reduced = pca.fit_transform(table_N.T)
coeff = np.transpose(pca.components_)
var_ratio = pca.explained_variance_ratio_
scalex = 1.0 / (reduced[:,0].max() - reduced[:,0].min())
scaley = 1.0 / (reduced[:,1].max() - reduced[:,1].min())
reduced[:,0] *= scalex
reduced[:,1] *= scaley
plot_pca(coeff, reduced, row_labels, col_labels, var_ratio)
from sklearn.decomposition import PCA
import numpy as np
table_N = aggregate_75_df.values
row_sum = table_N.sum(axis=1)
col_sum = table_N.sum(axis=0)
n = aggregate_75_df.values.sum()
expected = np.outer(row_sum, col_sum) / n
chisq = np.square(table_N - expected) / expected
residuals = (table_N - expected) / np.sqrt(expected)
# Standardized residuals
residuals = residuals / np.sqrt(n)
pca = PCA()
reduced = pca.fit_transform(residuals.T)
coeff = np.transpose(pca.components_)
var_ratio = pca.explained_variance_ratio_
scalex = 1.0 / (reduced[:,0].max() - reduced[:,0].min())
scaley = 1.0 / (reduced[:,1].max() - reduced[:,1].min())
reduced[:,0] *= scalex
reduced[:,1] *= scaley
plot_pca(coeff, reduced, row_labels, col_labels, var_ratio)
「文書-抽出語」表を作成する (出現回数 Top 1000語)
word_counts_1000_df = word_counts_df[0:1000]
merged_df = pd.merge(docs_df, word_counts_1000_df, how="inner", on="dict_key", suffixes=["", "_right"])
docs_1000_df = merged_df[["文書ID", "単語ID", "表層", "品詞", "カテゴリー", "エリア", "dict_key"]]
cross_1000_df = pd.crosstab(
[
docs_1000_df['カテゴリー'],
docs_1000_df['エリア'],
docs_1000_df['文書ID']
],
docs_1000_df['単語ID'], margins=False
)
cross_1000_df.columns = word_counts_1000_df["表層"]
「文書-抽出語」表を {0,1} に変換する
cross_1000_df[cross_1000_df > 0] = 1
print(cross_1000_df.shape)
display(cross_1000_df)
(9879, 1000)
| 表層 | 部屋 | 良い | ホテル | 風呂 | 美味しい | ない | スタッフ | 温泉 | よい | 立地 | ... | 長湯 | 懐かしい | 人柄 | 人達 | アンケート | トータル | 白 | レンタカー | ダメだ | スポット | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| カテゴリー | エリア | 文書ID | |||||||||||||||||||||
| A_レジャー | 01_登別 | 1 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 2 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 3 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 4 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 5 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| B_ビジネス | 10_福岡 | 9996 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 9997 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 9998 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 9999 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 10000 | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
9879 rows × 1000 columns
aggregate_df = pd.concat(
[
cross_1000_df.groupby(level='カテゴリー').sum(),
cross_1000_df.groupby(level='エリア').sum()
]
)
print(aggregate_df.shape)
display(aggregate_df)
(12, 1000)
| 表層 | 部屋 | 良い | ホテル | 風呂 | 美味しい | ない | スタッフ | 温泉 | よい | 立地 | ... | 長湯 | 懐かしい | 人柄 | 人達 | アンケート | トータル | 白 | レンタカー | ダメだ | スポット |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| A_レジャー | 2398 | 2046 | 757 | 1535 | 1430 | 880 | 888 | 1188 | 631 | 518 | ... | 18 | 18 | 18 | 13 | 9 | 8 | 13 | 6 | 11 | 13 |
| B_ビジネス | 2170 | 1655 | 1284 | 608 | 532 | 749 | 523 | 110 | 574 | 810 | ... | 1 | 1 | 1 | 6 | 6 | 11 | 4 | 13 | 8 | 6 |
| 01_登別 | 447 | 409 | 194 | 323 | 255 | 187 | 148 | 222 | 114 | 38 | ... | 6 | 4 | 1 | 3 | 3 | 3 | 2 | 1 | 1 | 1 |
| 02_草津 | 488 | 434 | 181 | 352 | 274 | 180 | 154 | 275 | 117 | 155 | ... | 4 | 7 | 8 | 4 | 3 | 2 | 2 | 1 | 2 | 1 |
| 03_箱根 | 548 | 436 | 134 | 326 | 355 | 202 | 212 | 212 | 133 | 57 | ... | 4 | 2 | 2 | 3 | 1 | 1 | 5 | 1 | 5 | 1 |
| 04_道後 | 416 | 349 | 191 | 181 | 174 | 130 | 135 | 225 | 137 | 176 | ... | 3 | 1 | 3 | 1 | 0 | 0 | 1 | 2 | 2 | 2 |
| 05_湯布院 | 499 | 418 | 57 | 353 | 372 | 181 | 239 | 254 | 130 | 92 | ... | 1 | 4 | 4 | 2 | 2 | 2 | 3 | 1 | 1 | 8 |
| 06_札幌 | 452 | 346 | 255 | 121 | 129 | 151 | 114 | 38 | 103 | 166 | ... | 0 | 0 | 1 | 1 | 1 | 2 | 0 | 7 | 4 | 1 |
| 07_名古屋 | 434 | 310 | 241 | 116 | 97 | 144 | 102 | 18 | 133 | 141 | ... | 0 | 0 | 0 | 1 | 2 | 0 | 2 | 1 | 1 | 1 |
| 08_東京 | 441 | 338 | 240 | 106 | 99 | 131 | 99 | 12 | 104 | 166 | ... | 0 | 0 | 0 | 2 | 0 | 1 | 1 | 0 | 0 | 1 |
| 09_大阪 | 431 | 317 | 297 | 135 | 88 | 162 | 93 | 20 | 121 | 161 | ... | 1 | 1 | 0 | 2 | 3 | 5 | 1 | 0 | 1 | 1 |
| 10_福岡 | 412 | 344 | 251 | 130 | 119 | 161 | 115 | 22 | 113 | 176 | ... | 0 | 0 | 0 | 0 | 0 | 3 | 0 | 5 | 2 | 2 |
12 rows × 1000 columns
word_counts = cross_1000_df.sum(axis=0).values
attr_counts = np.hstack(
[
all_df.value_counts('カテゴリー').values,
all_df.value_counts('エリア').values
]
)
jaccard_attrs_df = jaccard_attrs_coef(aggregate_df, attr_counts, word_counts, total=10000, conditional=False)
print(jaccard_attrs_df.shape)
display(jaccard_attrs_df)
(12, 1000)
| 表層 | 部屋 | 良い | ホテル | 風呂 | 美味しい | ない | スタッフ | 温泉 | よい | 立地 | ... | 長湯 | 懐かしい | 人柄 | 人達 | アンケート | トータル | 白 | レンタカー | ダメだ | スポット |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| A_レジャー | 0.334449 | 0.307438 | 0.000000 | 0.273716 | 0.258496 | 0.153070 | 0.160782 | 0.232485 | 0.113204 | 0.000000 | ... | 0.003599 | 0.003599 | 0.003599 | 0.002597 | 0.001798 | 0.000000 | 0.002598 | 0.000000 | 0.002196 | 0.002597 |
| B_ビジネス | 0.000000 | 0.000000 | 0.223033 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.146792 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.002196 | 0.000000 | 0.002597 | 0.000000 | 0.000000 |
| 01_登別 | 0.000000 | 0.095294 | 0.000000 | 0.114539 | 0.094200 | 0.076577 | 0.065400 | 0.106936 | 0.000000 | 0.000000 | ... | 0.005923 | 0.003941 | 0.000000 | 0.002953 | 0.002964 | 0.002953 | 0.001970 | 0.000000 | 0.000000 | 0.000000 |
| 02_草津 | 0.096063 | 0.101711 | 0.000000 | 0.126120 | 0.101935 | 0.073499 | 0.068232 | 0.135937 | 0.000000 | 0.071330 | ... | 0.003941 | 0.006917 | 0.007913 | 0.003941 | 0.002964 | 0.001967 | 0.001970 | 0.000000 | 0.001967 | 0.000000 |
| 03_箱根 | 0.109163 | 0.102227 | 0.000000 | 0.115726 | 0.136172 | 0.083230 | 0.096407 | 0.101630 | 0.064189 | 0.000000 | ... | 0.003941 | 0.001967 | 0.001967 | 0.002953 | 0.000000 | 0.000000 | 0.004941 | 0.000000 | 0.004931 | 0.000000 |
| 04_道後 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.108538 | 0.066248 | 0.081784 | ... | 0.002953 | 0.000000 | 0.002953 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.001967 | 0.001967 | 0.001967 |
| 05_湯布院 | 0.098442 | 0.097595 | 0.000000 | 0.126523 | 0.143629 | 0.073938 | 0.110037 | 0.124266 | 0.062651 | 0.000000 | ... | 0.000000 | 0.003941 | 0.003941 | 0.001967 | 0.001974 | 0.001967 | 0.002959 | 0.000000 | 0.000000 | 0.007913 |
| 06_札幌 | 0.000000 | 0.000000 | 0.091529 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.076781 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.001967 | 0.000000 | 0.006917 | 0.003941 | 0.000000 |
| 07_名古屋 | 0.000000 | 0.000000 | 0.086071 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.064189 | 0.064472 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.001974 | 0.000000 | 0.001970 | 0.000000 | 0.000000 | 0.000000 |
| 08_東京 | 0.000000 | 0.000000 | 0.085684 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.076781 | ... | 0.000000 | 0.000000 | 0.000000 | 0.001967 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| 09_大阪 | 0.000000 | 0.000000 | 0.108236 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.058061 | 0.074296 | ... | 0.000000 | 0.000000 | 0.000000 | 0.001967 | 0.002964 | 0.004931 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| 10_福岡 | 0.000000 | 0.000000 | 0.089964 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.081784 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.002953 | 0.000000 | 0.004931 | 0.001967 | 0.001967 |
12 rows × 1000 columns
df_list = []
for index, row in jaccard_attrs_df.iterrows():
df_list.append(row.iloc[np.argsort(row.values)[::-1][:10]].reset_index())
ranking_df = pd.DataFrame(pd.concat(df_list, axis=1))
ranking_df.columns = np.array([c for pair in [[x,f"jaccard"] for x in jaccard_attrs_df.index] for c in pair], dtype='object')
display(ranking_df)
| A_レジャー | jaccard | B_ビジネス | jaccard | 01_登別 | jaccard | 02_草津 | jaccard | 03_箱根 | jaccard | ... | 06_札幌 | jaccard | 07_名古屋 | jaccard | 08_東京 | jaccard | 09_大阪 | jaccard | 10_福岡 | jaccard | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 部屋 | 0.334449 | ホテル | 0.223033 | 風呂 | 0.114539 | 温泉 | 0.135937 | 美味しい | 0.136172 | ... | ホテル | 0.091529 | ホテル | 0.086071 | 駅 | 0.102041 | ホテル | 0.108236 | ホテル | 0.089964 |
| 1 | 良い | 0.307438 | 立地 | 0.146792 | 温泉 | 0.106936 | 風呂 | 0.126120 | 露天風呂 | 0.133758 | ... | 立地 | 0.076781 | 便利だ | 0.071669 | ホテル | 0.085684 | 駅 | 0.095941 | 便利だ | 0.086882 |
| 2 | 風呂 | 0.273716 | 便利だ | 0.133602 | 良い | 0.095294 | 宿 | 0.119559 | 風呂 | 0.115726 | ... | 便利だ | 0.076490 | 駅 | 0.070913 | 便利だ | 0.077703 | 便利だ | 0.080135 | 立地 | 0.081784 |
| 3 | 美味しい | 0.258496 | 駅 | 0.124465 | 美味しい | 0.094200 | 美味しい | 0.101935 | 部屋 | 0.109163 | ... | 綺麗だ | 0.071357 | 綺麗だ | 0.069208 | 立地 | 0.076781 | 立地 | 0.074296 | 駅 | 0.063881 |
| 4 | 温泉 | 0.232485 | 綺麗だ | 0.123283 | バイキング | 0.089563 | 良い | 0.101711 | 良い | 0.102227 | ... | 浴場 | 0.069540 | フロント | 0.065673 | 近い | 0.070529 | 綺麗だ | 0.072435 | フロント | 0.062741 |
| 5 | スタッフ | 0.160782 | フロント | 0.106943 | 残念だ | 0.078018 | 部屋 | 0.096063 | 温泉 | 0.101630 | ... | フロント | 0.064498 | 立地 | 0.064472 | 綺麗だ | 0.063872 | フロント | 0.067441 | 綺麗だ | 0.060169 |
| 6 | ない | 0.153070 | 近い | 0.091118 | ない | 0.076577 | 最高 | 0.090361 | 宿 | 0.096697 | ... | 広い | 0.062713 | よい | 0.064189 | 快適だ | 0.063002 | 快適だ | 0.064317 | トイレ | 0.052975 |
| 7 | 宿 | 0.152066 | 快適だ | 0.089507 | 夕食 | 0.075641 | 夕食 | 0.085382 | スタッフ | 0.096407 | ... | 快適だ | 0.055828 | 近い | 0.058531 | コンビニ | 0.058697 | 広い | 0.064265 | コンビニ | 0.052669 |
| 8 | 露天風呂 | 0.144101 | アメニティ | 0.072060 | 種類 | 0.075444 | ない | 0.073499 | 夕食 | 0.094586 | ... | 駅 | 0.055687 | アメニティ | 0.056187 | フロント | 0.055191 | 近い | 0.063164 | いい | 0.051754 |
| 9 | 最高 | 0.131658 | コンビニ | 0.069282 | 露天風呂 | 0.073583 | 大変だ | 0.072688 | ない | 0.083230 | ... | ベッド | 0.054865 | 快適だ | 0.055181 | アメニティ | 0.051965 | よい | 0.058061 | 近い | 0.050680 |
10 rows × 24 columns
import matplotlib.pyplot as plt
%matplotlib inline
def sort_and_plot(name, group):
# sort
sorted_columns = np.argsort(jaccard_attrs_df.loc[name].values)[::-1][:75]
group_cross_df = group.iloc[:,sorted_columns]
# plot
ax = fig.add_subplot(4, 3, i+1)
plot_wordcloud_ax(ax, " ".join(group_cross_df.columns))
ax.set_title(name)
fig = plt.figure(figsize=(16, 12))
i = 0
for name, group in cross_1000_df.groupby(level='カテゴリー'):
sort_and_plot(name, group)
i += 1
for sub_name, sub_group in group.groupby(level='エリア'):
sort_and_plot(sub_name, sub_group)
i += 1
plt.tight_layout()
plt.show()
import matplotlib.pyplot as plt
%matplotlib inline
def sort_and_plot(name, group):
# sort
sorted_columns = np.argsort(jaccard_attrs_df.loc[name].values)[::-1][:75]
group_cross_df = group.iloc[:,sorted_columns]
# plot
X = group_cross_df.values
X = csc_matrix(X)
Xc = (X.T * X)
Xc = np.triu(Xc.toarray())
group_cooccur_df = pd.DataFrame(Xc, columns=group_cross_df.columns, index=group_cross_df.columns)
group_jaccard_df = jaccard_coef(group_cooccur_df, group_cross_df)
word_counts = group_cross_df.sum(axis=0).values
ax = fig.add_subplot(4, 3, i+1)
plot_cooccur_network_ax(ax, group_jaccard_df, word_counts, np.sort(group_jaccard_df.values.reshape(-1))[::-1][60])
ax.set_title(name)
fig = plt.figure(figsize=(20, 28))
i = 0
for name, group in cross_1000_df.groupby(level='カテゴリー'):
sort_and_plot(name, group)
i += 1
for sub_name, sub_group in group.groupby(level='エリア'):
sort_and_plot(sub_name, sub_group)
i += 1
plt.tight_layout()
plt.show()